//**************************************************
// Description: extract UDP field from N*128-bit data 
//**************************************************
// note: do not execute CRC check !

//**************************************************
// include files
//**************************************************
`include "protocol_define.v"

module UDP_extract#(
    parameter IN_DATA_WIDTH     = 128 ,
    parameter IN_MOD_WIDTH      = 4  
        ) (
    input                           clk ,
    input                           rst_n ,
    // "unpack" interface
    output                          UDP_rdy ,
    input   [IN_DATA_WIDTH-1:0]     IPv4_payload ,
    input   [IN_MOD_WIDTH-1:0]      IPv4_mod ,
    input                           IPv4_sav ,
    input                           IPv4_val ,
    input                           IPv4_sop ,
    input                           IPv4_eop ,
    // "NETWORK" layer extracted field
    input   [31:0]                  IPv4_dst_IP ,
    input   [31:0]                  IPv4_src_IP ,
    // "DATA LINK" layer extracted field
    input   [47:0]                  DATALINK_src_mac ,
    input   [47:0]                  DATALINK_dst_mac ,

    // extracted interface
    input                           UDP_checked_rdy ,
    output  [IN_DATA_WIDTH-1:0]     UDP_checked_data ,
    output  [IN_MOD_WIDTH-1:0]      UDP_checked_mod ,
    output                          UDP_checked_sav ,
    output                          UDP_checked_val ,
    output                          UDP_checked_sop ,
    output                          UDP_checked_eop ,

    // check and store interface
    output                          field_enable ,
    output  [47:0]                  DATALINK_dst_mac_extracted ,
    output  [47:0]                  DATALINK_src_mac_extracted ,
    output  [31:0]                  NETWORK_src_IP_extracted ,
    output  [31:0]                  NETWORK_dst_IP_extracted ,
    output  [15:0]                  TRANSPORT_src_port_extracted ,
    output  [15:0]                  TRANSPORT_dst_port_extracted ,
    input                           check_result ,
    input                           check_sucess
) ;
//**************************************************
// FSM
//**************************************************
    //----------------------------------------------
    // signal declare
    //----------------------------------------------
    // IP payload delay
    reg     [IN_DATA_WIDTH-1:0]     IPv4_payload_ff1 ;
    reg     [IN_MOD_WIDTH-1:0]      IPv4_mod_ff1 ;
    reg                             IPv4_eop_ff1 ;
    reg     [IN_DATA_WIDTH-1:0]     IPv4_payload_ff2 ;
    reg     [IN_MOD_WIDTH-1:0]      IPv4_mod_ff2 ;
    reg                             IPv4_eop_ff2 ;
    reg     [IN_DATA_WIDTH-1:0]     IPv4_payload_ff3 ;
    reg     [IN_MOD_WIDTH-1:0]      IPv4_mod_ff3 ;
    reg                             IPv4_eop_ff3 ;
    reg     [IN_DATA_WIDTH-1:0]     IPv4_payload_ff4 ;
    reg     [IN_MOD_WIDTH-1:0]      IPv4_mod_ff4 ;
    reg                             IPv4_eop_ff4 ;
    reg     [IN_DATA_WIDTH-1:0]     IPv4_payload_ff5 ; // "ff5" is no use
    reg     [IN_MOD_WIDTH-1:0]      IPv4_mod_ff5 ;
    reg                             IPv4_eop_ff5 ;
    // check and store 
    reg                             field_enable_r ;
    reg     [47:0]                  DATALINK_dst_mac_extracted_r ;
    reg     [47:0]                  DATALINK_src_mac_extracted_r ;
    reg     [31:0]                  NETWORK_src_IP_extracted_r ;
    reg     [31:0]                  NETWORK_dst_IP_extracted_r ;
    reg     [15:0]                  TRANSPORT_src_port_extracted_r ;
    reg     [15:0]                  TRANSPORT_dst_port_extracted_r ;
    // UDP packet data payload
    reg     [IN_DATA_WIDTH-1:0]     UDP_checked_data_r ;
    reg     [IN_MOD_WIDTH-1:0]      UDP_checked_mod_r ;
    reg                             UDP_checked_sav_r ;
    reg                             UDP_checked_val_r ;
    reg                             UDP_checked_sop_r ;
    reg                             UDP_checked_eop_r ;

    //----------------------------------------------
    // FSM declare
    //----------------------------------------------
    localparam EXTRACT_IDLE     = 3'd0 ;
    localparam EXTRACT_WAIT     = 3'd1 ;
    localparam EXTRACT_HEAD     = 3'd2 ;
    localparam EXTRACT_CHECK    = 3'd3 ;
    localparam EXTRACT_DATA     = 3'd4 ;
    localparam EXTRACT_FAIL     = 3'd5 ;
    localparam EXTRACT_END      = 3'd6 ;

    reg     [2:0]       udp_extract_state_c ;
    reg     [2:0]       udp_extract_state_n ;
    // FSM (1)
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            udp_extract_state_c <= EXTRACT_IDLE ;
        end
        else begin
            udp_extract_state_c <= udp_extract_state_n ;
        end
    end
    // FSM (2)
    always @(*) begin
        case(udp_extract_state_c)
            EXTRACT_IDLE: begin
                if( (IPv4_sav == 1'b1) && (UDP_checked_rdy == 1'b1) )
                    udp_extract_state_n = EXTRACT_WAIT ;
                else
                    udp_extract_state_n = EXTRACT_IDLE ;
            end

            EXTRACT_WAIT: begin
                if(IPv4_sop == 1'b1)
                    udp_extract_state_n = EXTRACT_HEAD ;
                else
                    udp_extract_state_n = EXTRACT_WAIT ;
            end

            EXTRACT_HEAD: begin
                udp_extract_state_n = EXTRACT_CHECK ;
            end

            EXTRACT_CHECK: begin
                if(check_result == 1'b1) begin
                    if(check_sucess == 1'b1)
                        udp_extract_state_n = EXTRACT_DATA ;
                    else
                        udp_extract_state_n = EXTRACT_FAIL ;
                end
                else
                    udp_extract_state_n = EXTRACT_CHECK ;
            end

            EXTRACT_DATA: begin
                if(IPv4_eop_ff2 == 1'b1)
                    udp_extract_state_n = EXTRACT_END ;
                else
                    udp_extract_state_n = EXTRACT_DATA ;
            end

            EXTRACT_FAIL: begin
                if(IPv4_eop_ff2 == 1'b1)
                    udp_extract_state_n = EXTRACT_END ;
                else
                    udp_extract_state_n = EXTRACT_FAIL ;
            end

            EXTRACT_END: begin
                udp_extract_state_n = EXTRACT_IDLE ;
            end

            default: begin
                udp_extract_state_n = EXTRACT_IDLE ;
            end
        endcase
    end
    // FSM (3)
    // "IP payload" delay
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            IPv4_payload_ff1 <= {(IN_DATA_WIDTH){1'b0}} ;
            IPv4_mod_ff1 <= {(IN_MOD_WIDTH){1'b0}} ;
            IPv4_eop_ff1 <= 1'b0 ;
            IPv4_payload_ff2 <= {(IN_DATA_WIDTH){1'b0}} ;
            IPv4_mod_ff2 <= {(IN_MOD_WIDTH){1'b0}} ;
            IPv4_eop_ff2 <= 1'b0 ;
            IPv4_payload_ff3 <= {(IN_DATA_WIDTH){1'b0}} ;
            IPv4_mod_ff3 <= {(IN_MOD_WIDTH){1'b0}} ;
            IPv4_eop_ff3 <= 1'b0 ;
            IPv4_payload_ff4 <= {(IN_DATA_WIDTH){1'b0}} ;
            IPv4_mod_ff4 <= {(IN_MOD_WIDTH){1'b0}} ;
            IPv4_eop_ff4 <= 1'b0 ;
            IPv4_payload_ff5 <= {(IN_DATA_WIDTH){1'b0}} ;
            IPv4_mod_ff5 <= {(IN_MOD_WIDTH){1'b0}} ;
            IPv4_eop_ff5 <= 1'b0 ;
        end
        else begin
            IPv4_payload_ff1 <= IPv4_payload ;
            IPv4_mod_ff1 <= IPv4_mod ;
            IPv4_eop_ff1 <= IPv4_eop ;
            IPv4_payload_ff2 <= IPv4_payload_ff1 ;
            IPv4_mod_ff2 <= IPv4_mod_ff1 ;
            IPv4_eop_ff2 <= IPv4_eop_ff1 ;
            IPv4_payload_ff3 <= IPv4_payload_ff2 ;
            IPv4_mod_ff3 <= IPv4_mod_ff2 ;
            IPv4_eop_ff3 <= IPv4_eop_ff2 ;
            IPv4_payload_ff4 <= IPv4_payload_ff3 ;
            IPv4_mod_ff4 <= IPv4_mod_ff3 ;
            IPv4_eop_ff4 <= IPv4_eop_ff3 ;
            IPv4_payload_ff5 <= IPv4_payload_ff4 ;
            IPv4_mod_ff5 <= IPv4_mod_ff4 ;
            IPv4_eop_ff5 <= IPv4_eop_ff4 ;
        end
    end
    // get "UDP_rdy"
    assign UDP_rdy = ( (udp_extract_state_c == EXTRACT_IDLE) || (udp_extract_state_c == EXTRACT_WAIT) )?1'b1:1'b0 ;
    
    //----------------------------------------------
    // extracted data
    //----------------------------------------------
    // extracted field
    always @(posedge clk or negedge rst_n) begin
        if( !rst_n ) begin
            DATALINK_dst_mac_extracted_r <= {(48){1'b0}} ;
            DATALINK_src_mac_extracted_r <= {(48){1'b0}} ;
            NETWORK_src_IP_extracted_r <= {(32){1'b0}} ;
            NETWORK_dst_IP_extracted_r <= {(32){1'b0}} ;
            TRANSPORT_src_port_extracted_r <= {(16){1'b0}} ;
            TRANSPORT_dst_port_extracted_r <= {(16){1'b0}} ;
        end
        else if( (udp_extract_state_c == EXTRACT_WAIT) && (IPv4_sop == 1'b1) ) begin
            DATALINK_dst_mac_extracted_r <= DATALINK_dst_mac ;
            DATALINK_src_mac_extracted_r <= DATALINK_src_mac ;
            NETWORK_src_IP_extracted_r <= IPv4_src_IP ;
            NETWORK_dst_IP_extracted_r <= IPv4_dst_IP ;
            TRANSPORT_src_port_extracted_r <= IPv4_payload[(IN_DATA_WIDTH-1)    -:16] ;
            TRANSPORT_dst_port_extracted_r <= IPv4_payload[(IN_DATA_WIDTH-1-16) -:16] ;
        end
        else begin
            DATALINK_dst_mac_extracted_r <= DATALINK_dst_mac_extracted_r ;
            DATALINK_src_mac_extracted_r <= DATALINK_src_mac_extracted_r ;
            NETWORK_src_IP_extracted_r <= NETWORK_src_IP_extracted_r ;
            NETWORK_dst_IP_extracted_r <= NETWORK_dst_IP_extracted_r ;
            TRANSPORT_src_port_extracted_r <= TRANSPORT_src_port_extracted_r ;
            TRANSPORT_dst_port_extracted_r <= TRANSPORT_dst_port_extracted_r ;
        end
    end
    // extracted field enable
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            field_enable_r <= 1'b0 ;
        else if( (udp_extract_state_c == EXTRACT_WAIT) && (IPv4_sop == 1'b1) )
            field_enable_r <= 1'b1 ;
        else 
            field_enable_r <= 1'b0 ;
    end
    assign field_enable = field_enable_r ;
    assign DATALINK_dst_mac_extracted = DATALINK_dst_mac_extracted_r ;
    assign DATALINK_src_mac_extracted = DATALINK_src_mac_extracted_r ;
    assign NETWORK_src_IP_extracted = NETWORK_src_IP_extracted_r ;
    assign NETWORK_dst_IP_extracted = NETWORK_dst_IP_extracted_r ;
    assign TRANSPORT_src_port_extracted = TRANSPORT_src_port_extracted_r ;
    assign TRANSPORT_dst_port_extracted = TRANSPORT_dst_port_extracted_r ;



    //----------------------------------------------
    // UDP payload
    //----------------------------------------------
    // FSM delay
    reg     [2:0]       udp_extract_state_c_ff1 ;
    reg     [2:0]       udp_extract_state_c_ff2 ;
    reg     [2:0]       udp_extract_state_c_ff3 ;
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            udp_extract_state_c_ff1 <= EXTRACT_IDLE ;
            udp_extract_state_c_ff2 <= EXTRACT_IDLE ;
            udp_extract_state_c_ff3 <= EXTRACT_IDLE ;
        end
        else begin
            udp_extract_state_c_ff1 <= udp_extract_state_c ;
            udp_extract_state_c_ff2 <= udp_extract_state_c_ff1 ;
            udp_extract_state_c_ff3 <= udp_extract_state_c_ff2 ;
        end
    end

    // "sav" signal
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            UDP_checked_sav_r <= 1'b0 ;
        end
        else if((udp_extract_state_c == EXTRACT_CHECK) && (check_result & check_sucess)) begin
            UDP_checked_sav_r <= 1'b1 ;
        end
        else if( (udp_extract_state_c != EXTRACT_DATA) ) begin
            UDP_checked_sav_r <= 1'b0 ;
        end
        else begin
            UDP_checked_sav_r <= UDP_checked_sav_r ;
        end
    end
    // "val" "sop" "eop" signal
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            UDP_checked_sop_r <= 1'b0 ;
        else if( (udp_extract_state_c == EXTRACT_DATA) && 
                    (udp_extract_state_c_ff1 !=EXTRACT_DATA) )
            UDP_checked_sop_r <= 1'b1 ;
        else
            UDP_checked_sop_r <= 1'b0 ;
    end
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            UDP_checked_val_r <= 1'b0 ;
        else if( (udp_extract_state_c == EXTRACT_DATA) || 
                    ( (udp_extract_state_c_ff1 == EXTRACT_DATA) && ((IPv4_mod_ff3 == 4'd0) || (IPv4_mod_ff3 > 4'd8)) )  )
            UDP_checked_val_r <= 1'b1 ;
        else
            UDP_checked_val_r <= 1'b0 ;
    end
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            UDP_checked_eop_r <= 1'b0 ;
        else if(    ( (udp_extract_state_c == EXTRACT_DATA) && (IPv4_eop_ff2 && ((IPv4_mod_ff2 <= 4'd8) && (IPv4_mod_ff2 != 4'd0))) )    )  
            UDP_checked_eop_r <= 1'b1 ;
        else if(    ( (udp_extract_state_c_ff1 == EXTRACT_DATA) && (IPv4_eop_ff3 && ((IPv4_mod_ff3 > 4'd8) || (IPv4_mod_ff3 == 4'd0))) )    )  
            UDP_checked_eop_r <= 1'b1 ;
        else
            UDP_checked_eop_r <= 1'b0 ;
    end
    // data and mod
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            UDP_checked_data_r <= {(IN_DATA_WIDTH){1'b0}} ;
            UDP_checked_mod_r <= {(IN_MOD_WIDTH){1'b0}} ;
        end
        else if( udp_extract_state_c == EXTRACT_DATA ) begin
            if( IPv4_eop_ff2 ) begin
                if( (IPv4_mod_ff2 > 4'd8) || (IPv4_mod_ff2 == 4'd0) ) begin
                    UDP_checked_data_r <= {IPv4_payload_ff3[(IN_DATA_WIDTH-1-64):0], IPv4_payload_ff2[(IN_DATA_WIDTH-1)-:64]} ;
                    UDP_checked_mod_r <= {(IN_MOD_WIDTH){1'b0}} ;
                end
                else begin
                    UDP_checked_data_r <= {IPv4_payload_ff3[(IN_DATA_WIDTH-1-64):0], IPv4_payload_ff2[(IN_DATA_WIDTH-1)-:64]} ;
                    UDP_checked_mod_r <= 8+IPv4_mod_ff2 ;
                end
            end
            else begin
                UDP_checked_data_r <= {IPv4_payload_ff3[(IN_DATA_WIDTH-1-64):0], IPv4_payload_ff2[(IN_DATA_WIDTH-1)-:64]} ;
                UDP_checked_mod_r <= {(IN_MOD_WIDTH){1'b0}} ;
            end
        end
        else if( udp_extract_state_c_ff1 == EXTRACT_DATA ) begin
            if( (IPv4_mod_ff3 > 4'd8) || (IPv4_mod_ff3 == 4'd0) ) begin
                UDP_checked_data_r <= {IPv4_payload_ff3[(IN_DATA_WIDTH-1-64):0], IPv4_payload_ff2[(IN_DATA_WIDTH-1)-:64]} ;
                UDP_checked_mod_r <= IPv4_mod_ff3-8 ;
            end
            else begin
                UDP_checked_data_r <= {(IN_DATA_WIDTH){1'b0}} ;
                UDP_checked_mod_r <= {(IN_MOD_WIDTH){1'b0}} ;
            end
        end
        else begin
            UDP_checked_data_r <= {(IN_DATA_WIDTH){1'b0}} ;
            UDP_checked_mod_r <= {(IN_MOD_WIDTH){1'b0}} ;
        end
    end
    assign UDP_checked_data = UDP_checked_data_r ;
    assign UDP_checked_mod = UDP_checked_mod_r ;
    assign UDP_checked_sav = UDP_checked_sav_r ;
    assign UDP_checked_val = UDP_checked_val_r ;
    assign UDP_checked_sop = UDP_checked_sop_r ;
    assign UDP_checked_eop = UDP_checked_eop_r ;

endmodule